package dk.pun.dums.shared.character;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@SuppressWarnings("serial")
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class CharacterData implements Serializable {

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long key;
	@Persistent
	private Long userKey;
	@Persistent
	private Date createdDate;
	@Persistent
	private Date lastModifiedDate;
	@Persistent
	private CharacterStatus status;
	@Persistent
	private String name;
	@Persistent
	private Clan clan;
	@Persistent
	private int powerLevel;
	@Persistent
	private int ageBonus;
	@Persistent
	private int generationBonus;
	@Persistent
	private TalentType talentType;
	@Persistent
	private int physicalTalent;
	@Persistent
	private int mentalTalent;
	@Persistent @Embedded
	private CharacterAbilities characterAbilities;
	@Persistent @Embedded
	private CharacterDisciplines characterDisciplines;
	@Persistent
	private List<ExperiencePoints> experiences;
	
	public CharacterData () {
		this.createdDate = new Date();
		this.lastModifiedDate = this.createdDate;
		this.characterAbilities = new CharacterAbilities();
		this.characterDisciplines = new CharacterDisciplines();
		this.experiences = new ArrayList<ExperiencePoints>();

		this.initNewCharacter();
	}

	private void initNewCharacter() {
		this.status = CharacterStatus.NEW;
		this.clan = Clan.ASSAMITE;
		this.powerLevel = 1;
		this.ageBonus = 1;
		this.generationBonus = 1;
		this.talentType = this.clan.getTalentTypes()[0];
		this.physicalTalent = 1;
		this.mentalTalent = 1;
		this.characterAbilities.setClanAbility(this.clan);		
	}
	
	public Long getKey() {
		return this.key;
	}

	public void setUserKey(Long userKey) {
		this.userKey = userKey;
	}
	
	public Long getUserKey() {
		return this.userKey;
	}
	
	public void setCreatedDate(final Date createdDate) {
		this.createdDate = createdDate;
	}
	
	public Date getCreatedDate() {
		return this.createdDate;
	}

	public void setLastModifiedDate(final Date lastModifiedDate) {
		this.lastModifiedDate = lastModifiedDate;
	}
	
	public Date getLastModifiedDate() {
		return this.lastModifiedDate;
	}

	public void setStatus(final CharacterStatus status) {
		this.status = status;
		this.updateLastModifiedDate();
	}
	
	public CharacterStatus getStatus() {
		return this.status;
	}
	
	public String getName() {
		return this.name;
	}

	public void setName(final String name) {
		if (name == null) {
			throw new IllegalArgumentException("Name must not be null.");
		} else {
			this.name = name;
		}
		this.updateLastModifiedDate();
	}

	public Clan getClan() {
		return clan;
	}

	public void setClan(final Clan clan) {
		if (name == null) {
			throw new IllegalArgumentException("Clan must not be null.");
		} else {
			this.clan = clan;
			
			this.setTalentType(this.clan.getTalentTypes()[0]);
			this.characterAbilities.setClanAbility(this.clan);
		}
		this.updateLastModifiedDate();
	}

	public int getPowerLevel() {
		return powerLevel;
	}

	public void setPowerLevel(final int powerLevel) {
		if (powerLevel < 1 || powerLevel > 10) {
			throw new IllegalArgumentException("Illegal power level: " + powerLevel + ". Power level must be 1-10.");
		} else {
			this.powerLevel = powerLevel;

			this.ageBonus = powerLevel;
			this.generationBonus = powerLevel;
			this.resetMondaneTalent();
		}
		this.updateLastModifiedDate();
	}

	public int getAgeBonus() {
		return ageBonus;
	}

	public void setAgeBonus(final int ageBonus) {
		if (ageBonus < 1 || ageBonus > 10) {
			throw new IllegalArgumentException("Illegal age bonus: " + ageBonus + ". Age bonus must be 1-10.");
		} else {
			this.ageBonus = ageBonus;
			
			this.generationBonus = this.powerLevel*2 - ageBonus;
		}
		this.updateLastModifiedDate();
	}

	public boolean canIncreaseAgeBonus() {
		boolean result;
		if (this.ageBonus == 10 || this.generationBonus == 1 || this.ageBonus == 2*this.generationBonus || this.ageBonus+1 > (this.generationBonus-1)*2) {
			result = false;
		} else {
			result = true;
		}
		return result;
	}
	
	public boolean increaseAgeBonus() {
		final boolean result = this.canIncreaseAgeBonus();
		if (result) {
			this.setAgeBonus(this.ageBonus+1);
		}
		return result;
	}
	
	public int getGenerationBonus() {
		return generationBonus;
	}

	public void setGenerationBonus(final int generationBonus) {
		if (generationBonus < 1 || generationBonus > 10) {
			throw new IllegalArgumentException("Illegal generation bonus: " + generationBonus + ". Generation bonus must be 1-10.");
		} else {
			this.generationBonus = generationBonus;
			
			this.ageBonus = this.powerLevel*2 - generationBonus;
		}
		this.updateLastModifiedDate();
	}

	public boolean canIncreaseGenerationBonus() {
		boolean result;
		if (this.generationBonus == 10 || this.ageBonus == 1 || this.generationBonus == 2*this.ageBonus || this.generationBonus+1 > (this.ageBonus-1)*2) {
			result = false;
		} else {
			result = true;
		}
		return result;
	}
	
	public boolean increaseGenerationBonus() {
		final boolean result = this.canIncreaseGenerationBonus();
		if (result) {
			this.setGenerationBonus(this.generationBonus+1);
		}
		return result;
	}
	
	public TalentType getTalentType() {
		return this.talentType;
	}
	
	public void setTalentType(final TalentType talentType) {
		if (talentType == null) {
			throw new IllegalArgumentException("Talent type must not be null.");
		} else {
			this.talentType = talentType;
			
			this.resetMondaneTalent();
		}
		this.updateLastModifiedDate();
	}

	public int getPhysicalTalent() {
		return physicalTalent;
	}

	public void setPhysicalTalent(final int physicalTalent) {
		if (physicalTalent < 1 || physicalTalent > 6 || (physicalTalent > 5 && this.powerLevel < 10)) {
			throw new IllegalArgumentException("Illegal physical talent: " + physicalTalent);
		}
		this.physicalTalent = physicalTalent;
		this.mentalTalent = this.getMondaneTalentPoints() + 1 - physicalTalent;
		this.updateLastModifiedDate();
	}
	
	public boolean canIncreasePhysicalTalent() {
		boolean result;
		if (this.physicalTalent >= 6 || this.mentalTalent <= 1 || (this.powerLevel < 10 && this.physicalTalent >= 5)) {
			result = false;
		} else {
			result = true;
		}
		return result;
	}
	
	public boolean increasePhysicalTalent() {
		final boolean result = this.canIncreasePhysicalTalent();
		if (result) {
			this.setPhysicalTalent(this.physicalTalent+1);
		}
		return result;
	}

	public int getMentalTalent() {
		return mentalTalent;
	}

	public void setMentalTalent(final int mentalTalent) {
		if (mentalTalent < 1 || mentalTalent > 6 || (mentalTalent > 5 && this.powerLevel < 10)) {
			throw new IllegalArgumentException("Illegal mental talent: " + mentalTalent);
		}
		this.mentalTalent = mentalTalent;
		this.physicalTalent = this.getMondaneTalentPoints() + 1 - mentalTalent;
		this.updateLastModifiedDate();
	}

	public boolean canIncreaseMentalTalent() {
		boolean result;
		if (this.mentalTalent >= 6 || this.physicalTalent <= 1 || (this.powerLevel < 10 && this.mentalTalent >= 5)) {
			result = false;
		} else {
			result = true;
		}
		return result;
	}
	
	public boolean increaseMentalTalent() {
		final boolean result = this.canIncreaseMentalTalent();
		if (result) {
			this.setMentalTalent(this.mentalTalent+1);
		}
		return result;
	}

	public int getAbilityPoints(final Ability ability) {
		if (ability == null) {
			throw new IllegalArgumentException("Ability must not be null.");
		}
		int points;
		if (ability.getAbilityType() == AbilityType.PHYSICAL) {
			points = this.physicalTalent;
		} else {
			points = this.mentalTalent;
		}
		if (this.characterAbilities.isTrainedAbility(ability)) {
			points += 3;
		}
		return points;
	}
	
	private void resetMondaneTalent() {
		final int mondaneTalent = this.getMondaneTalentPoints();
		this.physicalTalent = mondaneTalent/2;
		this.mentalTalent = mondaneTalent/2;
		if (mondaneTalent%2 == 0) {
			if (this.talentType == TalentType.PHYSICAL) {
				this.physicalTalent++;
			} else {
				this.mentalTalent++;
			}
		} else {
			this.physicalTalent++;
			this.mentalTalent++;
		}
		this.updateLastModifiedDate();
	}
	
	public CharacterAbilities getTrainedAbilities() {
		return characterAbilities;
	}

	public void setTrainedAbilities(final CharacterAbilities characterAbilities) {
		this.characterAbilities = characterAbilities;
		this.updateLastModifiedDate();
	}
	
	public CharacterDisciplines getLearnedDisciplines() {
		return this.characterDisciplines;
	}
	
	public List<ExperiencePoints> getExperiences() {
		return this.experiences;
	}
	
	public void addExperience(ExperiencePoints experience) {
		this.experiences.add(experience);
	}
	
	public int getTotalExperiencePoints() {
		int result = 0;
		for (ExperiencePoints experience : this.experiences) {
			result += experience.getPoints();
		}
		return result;
	}
	
	public void copyContent(final CharacterData other) {
		this.lastModifiedDate = other.getLastModifiedDate();
		this.status = other.getStatus();
		this.name = other.getName();
		this.clan = other.getClan();
		this.powerLevel = other.getPowerLevel();
		this.ageBonus = other.getAgeBonus();
		this.generationBonus = other.getGenerationBonus();
		this.talentType = other.getTalentType();
		this.physicalTalent = other.getPhysicalTalent();
		this.mentalTalent = other.getMentalTalent();
		this.characterAbilities = other.getTrainedAbilities();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		CharacterData other = (CharacterData) obj;
		if (key == null) {
			if (other.key != null) {
				return false;
			}
		} else if (!key.equals(other.key)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "CharacterData [key=" + key + ", name=" + name + "]";
	}

	private void updateLastModifiedDate() {
		this.lastModifiedDate = new Date();
	}

	public int getMondaneTalentPoints() {
		final AgeBonus ageBonus = AgeBonus.getAgeBonus(this.getAgeBonus());
		return ageBonus.getMondaneTalent();
	}
	
	public int getDisciplinePoints() {
		final GenerationBonus generationBonus = GenerationBonus.getGenerationBonus(this.getGenerationBonus());
		return generationBonus.getDisciplines();
	}
	
	public int getUsedDisciplinePoints() {
		return this.getLearnedDisciplines().getUsedPoints();
	}
	
	public int getAvailableDisciplinePoints() {
		return 0;
	}
	
	public int getMondaneAdvantagePoints() {
		final AgeBonus ageBonus = AgeBonus.getAgeBonus(this.getAgeBonus());
		return ageBonus.getMondaneAdvantages();
	}
	
	public int getUsedMondaneAdvantagePoints() {
		return 0;
	}
	
	public int getAvailableMondaneAdvantagePoints() {
		return 0;
	}
	
	public int getSupernaturalAdvantagePoints() {
		final GenerationBonus generationBonus = GenerationBonus.getGenerationBonus(this.getGenerationBonus());
		return generationBonus.getSupernaturalAdvantages();
	}
	
	public int getUsedSupernaturalAdvantagePoints() {
		return 0;
	}
	
	public int getAvailableSupernaturalAdvantagePoints() {
		return 0;
	}
	
	public int getRessourcePoints() {
		final AgeBonus ageBonus = AgeBonus.getAgeBonus(this.getAgeBonus());
		return ageBonus.getRessources();
	}
	
	public int getUsedRessourcePoints() {
		return 0;
	}
	
	public int getAvailableRessourcePoints() {
		int result = this.getRessourcePoints() - this.getUsedRessourcePoints() + this.getDisadvantagePoints() + this.getExperiencePoints();
		int d = this.getAvailableDisciplinePoints() - this.getUsedDisciplinePoints();
		if (d < 0) {
			result += d;
		}
		int ma = this.getAvailableMondaneAdvantagePoints() - this.getUsedMondaneAdvantagePoints();
		if (ma < 0) {
			result += ma;
		}
		int sa = this.getAvailableSupernaturalAdvantagePoints() - this.getUsedSupernaturalAdvantagePoints();
		if (sa < 0) {
			result += sa;
		}
		return result;
	}
	
	public int getDisadvantagePoints() {
		return 0;
	}
	
	public int getExperiencePoints() {
		return 0;
	}
	
	public boolean canAddAdvantage(Advantage advantage) {
		boolean result;
		if (advantage.getAdvantageType().equals(AdvantageType.SUPERNATURAL)) {
			result = this.getAvailableMondaneAdvantagePoints() >= advantage.getValue();
		} else {
			result = this.getAvailableSupernaturalAdvantagePoints() >= advantage.getValue();
		}
		return result;
	}
	
	public boolean canAddDiscipline(Discipline discipline) {
		return (this.getAvailableDisciplinePoints() >= discipline.getLevel());
	}

}
